constructor() {
super();
- this._initialized = false;
+ this._initializationPromise = null;
this._updateNotified = false;
this._extensions = new Map();
this._extensionOrder = [];
this._checkVersion = false;
- Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
+ Main.sessionMode.connect('updated', () => {
+ this._sessionUpdated();
+ });
}
init() {
});
this._installExtensionUpdates();
- this._sessionUpdated();
+ this._sessionUpdated().then(() => {
+ ExtensionDownloader.checkForUpdates();
+ });
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => {
ExtensionDownloader.checkForUpdates();
+
return GLib.SOURCE_CONTINUE;
});
- ExtensionDownloader.checkForUpdates();
}
get updatesSupported() {
return false;
}
- _callExtensionDisable(uuid) {
+ async _callExtensionDisable(uuid) {
let extension = this.lookup(uuid);
if (!extension)
return;
- if (extension.state != ExtensionState.ENABLED)
+ if (extension.state !== ExtensionState.ENABLED)
return;
extension.state = ExtensionState.DISABLING;
for (let i = 0; i < order.length; i++) {
let otherUuid = order[i];
try {
- this.lookup(otherUuid).stateObj.enable();
+ // eslint-disable-next-line no-await-in-loop
+ await this.lookup(otherUuid).stateObj.enable();
} catch (e) {
this.logExtensionError(otherUuid, e);
}
this._extensionOrder.splice(orderIdx, 1);
- if (extension.state != ExtensionState.ERROR) {
+ if (extension.state !== ExtensionState.ERROR) {
extension.state = ExtensionState.DISABLED;
this.emit('extension-state-changed', extension);
}
}
- _callExtensionEnable(uuid) {
+ async _callExtensionEnable(uuid) {
if (!this._extensionSupportsSessionMode(uuid))
return;
if (!extension)
return;
- if (extension.state == ExtensionState.INITIALIZED)
- this._callExtensionInit(uuid);
+ if (extension.state === ExtensionState.INITIALIZED)
+ await this._callExtensionInit(uuid);
- if (extension.state != ExtensionState.DISABLED)
+
+ if (extension.state !== ExtensionState.DISABLED)
return;
extension.state = ExtensionState.ENABLING;
}
try {
- extension.stateObj.enable();
+ await extension.stateObj.enable();
extension.state = ExtensionState.ENABLED;
this._extensionOrder.push(uuid);
this.emit('extension-state-changed', extension);
return extension.metadata.version === version;
}
- loadExtension(extension) {
+ async loadExtension(extension) {
// Default to error, we set success as the last step
extension.state = ExtensionState.ERROR;
let enabled = this._enabledExtensions.includes(extension.uuid) &&
this._extensionSupportsSessionMode(extension.uuid);
if (enabled) {
- if (!this._callExtensionInit(extension.uuid))
+ if (!await this._callExtensionInit(extension.uuid))
return;
- if (extension.state == ExtensionState.DISABLED)
- this._callExtensionEnable(extension.uuid);
+
+ if (extension.state === ExtensionState.DISABLED)
+ await this._callExtensionEnable(extension.uuid);
} else {
extension.state = ExtensionState.INITIALIZED;
}
this.emit('extension-state-changed', extension);
}
- unloadExtension(extension) {
+ async unloadExtension(extension) {
const { uuid, type } = extension;
// Try to disable it -- if it's ERROR'd, we can't guarantee that,
// but it will be removed on next reboot, and hopefully nothing
// broke too much.
- this._callExtensionDisable(uuid);
+ await this._callExtensionDisable(uuid);
extension.state = ExtensionState.UNINSTALLED;
this.emit('extension-state-changed', extension);
- // If we did install an importer, it is now cached and it's
- // impossible to load a different version
- if (type === ExtensionType.PER_USER && extension.imports)
+ // The extension is now cached and it's impossible to load a different version
+ if (type === ExtensionType.PER_USER && extension.isImported)
this._unloadedExtensions.set(uuid, extension.metadata.version);
this._extensions.delete(uuid);
return true;
}
- reloadExtension(oldExtension) {
+ async reloadExtension(oldExtension) {
// Grab the things we'll need to pass to createExtensionObject
// to reload it.
let { uuid, dir, type } = oldExtension;
return;
}
- this.loadExtension(newExtension);
+ await this.loadExtension(newExtension);
}
- _callExtensionInit(uuid) {
+ async _callExtensionInit(uuid) {
if (!this._extensionSupportsSessionMode(uuid))
return false;
let extensionModule;
let extensionState = null;
+ // TODO: This function does not have an async operation but when ESM is
+ // merged there will be a dynamic import here instead of `installImporter`.
+ await 0;
+
ExtensionUtils.installImporter(extension);
+
+ // Extensions can only be imported once, so add a property to avoid
+ // attempting to re-import an extension.
+ extension.isImported = true;
+
try {
extensionModule = extension.imports.extension;
} catch (e) {
if (extensionModule.init) {
try {
- extensionState = extensionModule.init(extension);
+ extensionState = await extensionModule.init(extension);
} catch (e) {
this.logExtensionError(uuid, e);
return false;
_updateCanChange(extension) {
let hasError =
- extension.state == ExtensionState.ERROR ||
- extension.state == ExtensionState.OUT_OF_DATE;
+ extension.state === ExtensionState.ERROR ||
+ extension.state === ExtensionState.OUT_OF_DATE;
let isMode = this._getModeExtensions().includes(extension.uuid);
let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY);
return extensions.filter(item => !disabledExtensions.includes(item));
}
- _onUserExtensionsEnabledChanged() {
- this._onEnabledExtensionsChanged();
+ async _onUserExtensionsEnabledChanged() {
+ await this._onEnabledExtensionsChanged();
this._onSettingsWritableChanged();
}
- _onEnabledExtensionsChanged() {
+ async _onEnabledExtensionsChanged() {
let newEnabledExtensions = this._getEnabledExtensions();
// Find and enable all the newly enabled extensions: UUIDs found in the
// new setting, but not in the old one.
- newEnabledExtensions
+ const extensionsToEnable = newEnabledExtensions
.filter(uuid => !this._enabledExtensions.includes(uuid) &&
- this._extensionSupportsSessionMode(uuid))
- .forEach(uuid => this._callExtensionEnable(uuid));
+ this._extensionSupportsSessionMode(uuid));
+ for (const uuid of extensionsToEnable) {
+ // eslint-disable-next-line no-await-in-loop
+ await this._callExtensionEnable(uuid);
+ }
// Find and disable all the newly disabled extensions: UUIDs found in the
// old setting, but not in the new one.
- this._extensionOrder
+ const extensionsToDisable = this._extensionOrder
.filter(uuid => !newEnabledExtensions.includes(uuid) ||
- !this._extensionSupportsSessionMode(uuid))
- .reverse().forEach(uuid => this._callExtensionDisable(uuid));
+ !this._extensionSupportsSessionMode(uuid));
+ // Reverse mutates the original array, but .filter() creates a new array.
+ extensionsToDisable.reverse();
+
+ for (const uuid of extensionsToDisable) {
+ // eslint-disable-next-line no-await-in-loop
+ await this._callExtensionDisable(uuid);
+ }
this._enabledExtensions = newEnabledExtensions;
}
}
}
- _onVersionValidationChanged() {
+ async _onVersionValidationChanged() {
const checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);
if (checkVersion === this._checkVersion)
return;
// Disabling extensions modifies the order array, so use a copy
let extensionOrder = this._extensionOrder.slice();
- // Disable enabled extensions in the reverse order first to avoid
+ // Disable enabled extensions first to avoid
// the "rebasing" done in _callExtensionDisable...
- extensionOrder.slice().reverse().forEach(uuid => {
- this._callExtensionDisable(uuid);
- });
+ this._disableAllExtensions();
// ...and then reload and enable extensions in the correct order again.
- [...this._extensions.values()].sort((a, b) => {
+ const extensionsToReload = [...this._extensions.values()].sort((a, b) => {
return extensionOrder.indexOf(a.uuid) - extensionOrder.indexOf(b.uuid);
- }).forEach(extension => this.reloadExtension(extension));
+ });
+ for (const extension of extensionsToReload) {
+ // eslint-disable-next-line no-await-in-loop
+ await this.reloadExtension(extension);
+ }
}
_installExtensionUpdates() {
}
}
- _loadExtensions() {
- global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`,
- this._onEnabledExtensionsChanged.bind(this));
- global.settings.connect(`changed::${DISABLED_EXTENSIONS_KEY}`,
- this._onEnabledExtensionsChanged.bind(this));
- global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`,
- this._onUserExtensionsEnabledChanged.bind(this));
- global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`,
- this._onVersionValidationChanged.bind(this));
- global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`,
- this._onSettingsWritableChanged.bind(this));
- global.settings.connect(`writable-changed::${DISABLED_EXTENSIONS_KEY}`,
- this._onSettingsWritableChanged.bind(this));
+ async _loadExtensions() {
+ global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`, () => {
+ this._onEnabledExtensionsChanged();
+ });
+ global.settings.connect(`changed::${DISABLED_EXTENSIONS_KEY}`, () => {
+ this._onEnabledExtensionsChanged();
+ });
+ global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`, () => {
+ this._onUserExtensionsEnabledChanged();
+ });
+ global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`, () => {
+ this._onVersionValidationChanged();
+ });
+ global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`, () =>
+ this._onSettingsWritableChanged());
+ global.settings.connect(`writable-changed::${DISABLED_EXTENSIONS_KEY}`, () =>
+ this._onSettingsWritableChanged());
- this._onVersionValidationChanged();
+ await this._onVersionValidationChanged();
this._enabledExtensions = this._getEnabledExtensions();
return extension;
}).filter(extension => extension !== null);
- for (const extension of extensionObjects)
- this.loadExtension(extension);
+ for (const extension of extensionObjects) {
+ // eslint-disable-next-line no-await-in-loop
+ await this.loadExtension(extension);
+ }
}
- _enableAllExtensions() {
- if (!this._initialized) {
- this._loadExtensions();
- this._initialized = true;
- } else {
- this._enabledExtensions.forEach(uuid => {
- this._callExtensionEnable(uuid);
- });
+ async _enableAllExtensions() {
+ if (!this._initializationPromise)
+ this._initializationPromise = this._loadExtensions();
+
+ await this._initializationPromise;
+
+ for (const uuid of this._enabledExtensions) {
+ // eslint-disable-next-line no-await-in-loop
+ await this._callExtensionEnable(uuid);
}
}
- _disableAllExtensions() {
- if (this._initialized) {
- this._extensionOrder.slice().reverse().forEach(uuid => {
- this._callExtensionDisable(uuid);
- });
+
+ /**
+ * Disables all currently enabled extensions.
+ */
+ async _disableAllExtensions() {
+ // Wait for extensions to finish loading before starting
+ // to disable, otherwise some extensions may enable after
+ // this function.
+ if (this._initializationPromise)
+ await this._initializationPromise;
+
+ const extensionsToDisable = this._extensionOrder.slice();
+ // Extensions are disabled in the reverse order
+ // from when they were enabled.
+ extensionsToDisable.reverse();
+
+ for (const uuid of extensionsToDisable) {
+ // eslint-disable-next-line no-await-in-loop
+ await this._callExtensionDisable(uuid);
}
}
- _sessionUpdated() {
+ async _sessionUpdated() {
// Take care of added or removed sessionMode extensions
- this._onEnabledExtensionsChanged();
- this._enableAllExtensions();
+ await this._onEnabledExtensionsChanged();
+ await this._enableAllExtensions();
}
};